
import java.util.*;
public class Perceptron {
    private double[] weight = null;
    private double bias = 0;
    private double learningRate = 0.5;
    private PerceptronDataSet data = null;
    private int dimension = 0;

    public Perceptron(int dimension, PerceptronDataSet data) throws Exception {
        setDimension(dimension);
        setPerceptronDataSet(data);
    }

    public void train() throws Exception {
        train(10000);
    }

    public void train(int iterationMax) throws Exception {
        if (this.data == null)
            throw new LackDataException("Please input PerceptronDataSet before training.");
        if (this.weight == null) 
            weight = new double[dimension];
        //start training
        ArrayList<double[]> dataSet = data.getDataSet();
        ArrayList<Integer> labelSet = data.getLabelSet();
        int flag = 0;
        int iterationCount = 0;
        System.out.println("\nInitial weight: " + Arrays.toString(this.weight));        
        System.out.println("Initial bias: " + this.bias);
        while (flag < dataSet.size() && iterationCount < iterationMax) {
            for (int i = 0; i < dataSet.size(); i++) {
                while (needUpdating(dataSet.get(i), labelSet.get(i))) {
                    iterationCount++;
                    System.out.println("\nInteration: " + iterationCount);
                    updateWeightAndBias(dataSet.get(i), labelSet.get(i));
                    flag = 0;
                    
                }
                flag++;
            }
        }
        if (flag < dataSet.size()) {
            System.out.println("Please make sure the " +
                "PerceptronDataSet is linearly separable " +
                "or increase max number of iteration.");
        } else {
            System.out.println("Training completed successfully.");
        }
    }

    private boolean needUpdating(double[] vec, int label) {
        double result = 0;
        for (int i = 0; i < vec.length; i++) {
            result += vec[i] * this.weight[i];
        }
        result += bias;
        result *= label;
        return result <= 0;
    }

    private void updateWeightAndBias(double[] vec, int label) {
        for (int i = 0; i < vec.length; i++) {
            this.weight[i] = this.weight[i] + this.learningRate * label * vec[i];
        }
        this.bias = this.bias + this.learningRate * label;
        System.out.println("update from data: " + 
            Arrays.toString(vec) + " label: " + label);
        System.out.println("Weight: " + Arrays.toString(this.weight));
        System.out.println("Bias: " + this.bias);

	
    }
   
    public int getDimension() {
        return this.dimension;
    }

    public double[] getWeight() {
        return this.weight;
    }

    public double getBias() {
        return this.bias;
    }

    public double getLearningRate() {
        return this.learningRate;
    }

    public PerceptronDataSet getPerceptronDataSet() {
        return this.data;
    }

    private Perceptron setDimension(int dimension) throws Exception {
        if (dimension <= 0)
            throw new DimensionException(
                "Dimension must be a positive integer.");
        this.dimension = dimension;
        return this;
    }

    public Perceptron setWeight(double[] weight) throws Exception {
        if (weight.length != this.dimension) 
            throw new DimensionException("The dimension of " +
                "weight must be the same as Perceptron.");
        this.weight = weight;
        return this;
    }

    public Perceptron setBias(double bias) {
        this.bias = bias;
        return this;
    }

    public Perceptron setLearningRate(double learningRate) throws Exception {
        if (learningRate <= 0 || learningRate > 1) 
            throw new Exception("Please make sure 0 < learningRate <= 1");
        this.learningRate = learningRate;
        return this;
    }

    private Perceptron setPerceptronDataSet(PerceptronDataSet data) throws Exception {
        if (data.getDimension() != this.dimension)
            throw new DimensionException(
                "The dimension of PerceptronDataSet must be" +
                "the same as Perceptron.");
        this.data = data;
        return this;
    }
}
